package com.atguigu.tingshu.search.runnable;

import com.atguigu.tingshu.search.factory.ScheduleTaskThreadFactory;
import com.atguigu.tingshu.search.service.impl.SearchServiceImpl;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Description:
 * 这是一个专门用于【重新构建专辑id布隆过滤器】的任务类
 *
 * 由于系统中有可能实时有新增的专辑，所以我们需要构建当前类，用于执行重新构建布隆过滤器中的值的任务
 * 具体任务流程
 * 1.删除旧的布隆过滤器
 * 2.删除旧的布隆过滤器集合
 * 3.创建新的布隆过滤器
 * 4.给新的布隆过滤器重新赋值 --> 最新的专辑id列表
 * 5.使用新的布隆过滤器替代旧的布隆过滤器  -->  redisTemplate的rename()方法
 * @author Yanxinhao on 2025/2/7
 */
public class RebuildDistriBloomFilterRunnable implements Runnable{

    Logger logger = LoggerFactory.getLogger(this.getClass());

    private RedissonClient redissonClient;

    private SearchServiceImpl searchService;

    private StringRedisTemplate redisTemplate;

    public RebuildDistriBloomFilterRunnable(RedissonClient redissonClient, SearchServiceImpl searchService, StringRedisTemplate redisTemplate) {
        this.redissonClient = redissonClient;
        this.searchService = searchService;
        this.redisTemplate = redisTemplate;
    }

    @Override
    public void run() {
        /**
         * 1.删除旧的布隆过滤器
         * 2.删除旧的布隆过滤器集合
         * 3.创建新的布隆过滤器
         * 4.给新的布隆过滤器重新赋值 --> 最新的专辑id列表
         * 5.使用新的布隆过滤器替代旧的布隆过滤器  -->  redisTemplate的rename()方法
         *   这样的做法有失偏颇，我们如果先删除旧的布隆过滤器，万一代码执行异常
         *   导致旧的布隆过滤器被删除了，但是新的布隆过滤器没有被创建，那么就会导致布隆过滤器失效
         *   然后布隆过滤器一失效，就会导致缓存穿透，从而导致数据库查询，从而导致数据库压力过大，从而导致数据库崩溃
         *   因此我们要选择先创建新的布隆过滤器，并给其赋值
         *   然后使用lua脚本，使得删除旧过滤器操作和替换操作形成原子操作，满足这样的条件，就安全了
         */
        //1.创建新的布隆过滤器
        RBloomFilter<Object> newBloomFilter = redissonClient.getBloomFilter("albumIdBloomFilter:new");
        //2.给新的布隆过滤器初始化值，即add最新的专辑id集合数据
        List<Long> albumIdList = searchService.getAlbumIdList();
        if (!CollectionUtils.isEmpty(albumIdList)){
            newBloomFilter.tryInit(1000000L,0.03);
            for (Long id : albumIdList) {
                newBloomFilter.add(id);
            }
            logger.info("【！新的！】分布式布隆过滤器初始化完毕，内部id数量个数：" + newBloomFilter.count());
        }
        //3.将删除操作和替换操作使用lua脚本做一个原子操作
        String script = " redis.call(\"del\",KEYS[1])" + "  redis.call(\"del\",KEYS[2])" + "  redis.call(\"rename\",KEYS[3],KEYS[1])" + "  redis.call(\"rename\",KEYS[4],KEYS[2]) return 1";
        List<String> keys = List.of("albumIdBloomFilter","{albumIdBloomFilter}:config","albumIdBloomFilter:new","{albumIdBloomFilter:new}:config");
        Long execute = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), keys);
        if (execute == 1){
            logger.info("【！新的！】布隆过滤器已上线");
        } else {
            logger.error("新的布隆过滤器尝试上线失败,继续使用旧布隆过滤器");
        }

        //4.继续嵌套执行一层延时任务
        /**
         * 我们这样子做，在一个延时任务中结尾继续执行一个延时任务
         * 实现任务嵌套，这样其实延时任务就变成了一个定时任务
         * 这里就是每七天执行一次当前任务，即重建专辑id布隆过滤器
         */
        ScheduleTaskThreadFactory.getInstance().execute(this,7L, TimeUnit.DAYS);
    }
}